package dkbta.server.domain.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.log4j.Logger;

import dkbta.Config;
import dkbta.server.gui.Observable;
import dkbta.server.gui.Observer;
import dkbta.server.util.exceptions.FailedToReadQueryException;
import dkbta.server.util.query.Query;
import dkbta.server.util.query.Results;

/**
 * @author Arkady Mishiev
 *
 */
public class ClientStub extends Thread implements Observable{
	private Socket m_clientSocket;
	private String m_id;
	private boolean m_isConnected;
	private PrintWriter m_out ;
	private BufferedReader m_in;
	private String m_leftOver;	
	private static Logger s_logger;
	private LinkedBlockingQueue<Query> m_globalQueryOutbox;
	private LinkedBlockingQueue<Results> m_resultsInbox;
	private boolean m_shouldRun;
	private Observer m_logsPanelObserver;
        private Observer m_plansPanelObserver;
	
	public ClientStub(Socket socket, String id, LinkedBlockingQueue<Query> globalQueryOutbox) {
		super("ClientStub");
		m_globalQueryOutbox = globalQueryOutbox;
		m_clientSocket = socket;
		m_id = id;
		m_isConnected = true;
		m_leftOver = "";
		m_shouldRun = true;
		m_resultsInbox = new LinkedBlockingQueue<Results>();
		s_logger = Logger.getLogger(ClientStub.class);
		try {
			m_out = new PrintWriter(m_clientSocket.getOutputStream(), true);
			m_in = new BufferedReader(new InputStreamReader(m_clientSocket.getInputStream()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				
	}

	public void run(){
		while(m_shouldRun){
			try{
				/* query received from Client 
				 * client id is set
				 * */
				Query query = receiveQuery();                                
				s_logger.info("Moving query to outbox");
				m_logsPanelObserver.updateLogsPanel("Moving query to outbox");
                                m_plansPanelObserver.updatePlansPanel(query);
				m_globalQueryOutbox.put(query);
				s_logger.info("Begin to wait for results");
				m_logsPanelObserver.updateLogsPanel("Begin to wait for results");
				Results results = m_resultsInbox.take();
                                Thread.sleep(3000);
                                query.setState(Query.PlanState.RETURNED);
                                query.setFinishTime();
                                m_plansPanelObserver.updatePlansPanel(query);
				s_logger.info("End waiting for results. Results received. queryId :" + 
						results.getQueryId());
				m_logsPanelObserver.updateLogsPanel("End waiting for results." + 
						"Results received. queryId :" +	results.getQueryId());
				sendResults(results);				
			} 
			catch (FailedToReadQueryException e1) {
				s_logger.warn("Disconnected client");
				m_logsPanelObserver.updateLogsPanel("Disconnected client");
				closeConnection();
			}
			catch (InterruptedException e) {
				s_logger.warn("Interrupted while using inbox/outbox!");
				m_logsPanelObserver.updateLogsPanel("Interrupted while using inbox/outbox!");
				closeConnection();
			}	
		}
	}

	private String nextToken() throws IOException, FailedToReadQueryException  {
		String ans = m_leftOver;
		String toReturn = null;
		boolean readMore = true;
		final char[] charBuffer = new char[10000];
		final StringBuilder sb = new StringBuilder();
		int k ;
		while(readMore){
			k = m_in.read(charBuffer);
			if(k == -1){
				m_isConnected = false;
				throw new FailedToReadQueryException();
			}
			sb.append(charBuffer,0,k);
			ans = ans + sb.toString();
			if(ans.contains(Config.FRAME)){
				readMore = false;
				String left = ans.substring(ans.indexOf(Config.FRAME) + 
						Config.FRAME.length(),ans.length());
				m_leftOver = left;
				ans = ans.substring(0,ans.indexOf(Config.FRAME));
				toReturn = ans.replace(Config.FRAME,"");
			}        
		}
		return toReturn ;
	}	

	private void sendResults(Results results) {
		s_logger.info("Begin sending results. queryId :" + results.getQueryId() + ".");
		m_logsPanelObserver.updateLogsPanel("Begin sending results. queryId :" + results.getQueryId() + ".");
		String str = results.toString();
		m_out.print(str + Config.FRAME);
		m_out.flush();
		s_logger.info("End sending results. queryId :" + results.getQueryId() + ".");
		m_logsPanelObserver.updateLogsPanel("End sending results. queryId :" + results.getQueryId() + ".");
	}

	private Query receiveQuery() throws FailedToReadQueryException {
		s_logger.info("Begin to wait for query");
		m_logsPanelObserver.updateLogsPanel("Begin to wait for query");
		String query_str;
		try {
			query_str = nextToken();
			Query query = new Query(query_str, m_id);
			s_logger.info("End to wait for query. queryId :" + query.getId() + ".");
			m_logsPanelObserver.updateLogsPanel("End to wait for query. queryId :" + query.getId() + ".");
			return query;			
		} 
		catch (IOException e) {
			m_isConnected = false;
			throw new FailedToReadQueryException();
		}		
	}		

	private void closeConnection() {
		m_shouldRun = false;
		s_logger.warn("Closed clients connection " + m_id);
		m_logsPanelObserver.updateLogsPanel("Closed clients connection " + m_id);
	}

	public void acceptResults(Results results) {
		try {
			s_logger.info("Results received");
			m_resultsInbox.put(results);
			s_logger.info("Results moved into inbox");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			s_logger.warn("Interrupted while inserting results into inbox");
		}
	}

	@Override
	public void addCUPanelObserver(Observer o) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addLogsPanelObserver(Observer o) {
			m_logsPanelObserver = o;		
	}

	@Override
	public void addQueriesPanelObserver(Observer o) {
		m_plansPanelObserver = o;
	}

}
